package com.term.dao.impl;

import com.term.domain.BaseDomain;
import com.term.domain.utils.DomainPage;
import com.term.dao.IBaseDao;
import com.term.domain.common.Condition;
import com.term.dao.utils.QueryUtil;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Stream;


/**
 * Created by chenGang on 2017/1/6.
 */
public class BaseDaoImpl<T extends BaseDomain> implements IBaseDao<T> {

    @PersistenceContext
    protected EntityManager em;

    protected static final String andPattern = " and ";

    protected static final String wherePattern = " c where 1 = 1";

    protected static final String spacePattern = " ";

    @Override
    public T refresh(T t) {
        em.refresh(t);
        return t;
    }

    @Override
    @Transactional
    public T save(T t) {
        T res = em.merge(t);
        return res;
    }

    @Override
    public T getEntityById(Class<T> clazz, Object id) {
        if (null == id) {
            return null;
        }
        StringBuffer stringBuffer = new StringBuffer("select c from ");
        stringBuffer.append(clazz.getName()).append(wherePattern).append(andPattern).append(" c.id = ").append(id);

        Query query = em.createQuery(stringBuffer.toString());

        query.setMaxResults(1);

        try {
            return (T) query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    @Override
    public T getEntityByField(Class<T> clazz, String fieldName, Object fieldValue) {
        StringBuilder buffer = new StringBuilder("select c from ");
        buffer.append(clazz.getName()).append(wherePattern).append(andPattern).append("c.").append(fieldName).append(spacePattern).append(this.getFilterConditionByFieldValue(fieldValue, 1, false));

        Query query = em.createQuery(buffer.toString());
        if (null != fieldValue) {
            query.setParameter(1, fieldValue);
        }

        query.setMaxResults(1);

        try {
            return (T) query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    @Override
    public List<T> getEntitiesByField(Class<T> clazz, String fieldName, Object fieldValue) {
        Map<String, Object> fieldNameValueMap = null;
        if (null != fieldName && null != fieldValue) {
            fieldNameValueMap = new HashMap<>(1);
            fieldNameValueMap.put(fieldName, fieldValue);
        }
        return this.getEntitiesByFieldList(clazz, fieldNameValueMap);
    }

    @Override
    public List<T> getEntitiesByFieldList(Class<T> clazz, Map<String, Object> fieldNameValueMap) {
        return this.getEntitiesByFieldList(clazz, fieldNameValueMap, null, null);
    }

    @Override
    public long getEntityTotalCount(Class<T> clazz, String fieldName, Object fieldValue) {
        Map<String, Object> fieldNameValueMap = null;
        if (null != fieldName && null != fieldValue) {
            fieldNameValueMap = new HashMap<>(1);
            fieldNameValueMap.put(fieldName, fieldValue);
        }
        return this.getEntityTotalCount(clazz, fieldNameValueMap);
    }

    @Override
    public long getEntityTotalCount(Class<T> clazz) {
        return this.getEntityTotalCount(clazz, null);
    }

    @Override
    public long getEntityTotalCount(Class<T> clazz, Map<String, Object> fieldNameValueMap) {
        StringBuilder buffer = new StringBuilder("select count(c.id) from ");
        buffer.append(clazz.getName()).append(wherePattern);

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            buffer.append(andPattern);
            this.concatFilterConditionsByMap(fieldNameValueMap, buffer);
        }

        Query query = em.createQuery(buffer.toString());

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            this.setFilterConditionsWithValues(fieldNameValueMap.values(), query);
        }

        return this.getTotalCount(query);
    }

    @Override
    public List<T> getAllEntities(Class<T> clazz) {
        return this.getEntitiesByFieldList(clazz, null);
    }

    @Override
    public DomainPage getAllEntitiesByPage(Class<T> clazz, int pageIndex, int pageSize) {
        return this.getAllEntitiesByPage(clazz, pageIndex, pageSize, null, null);
    }

    @Override
    public DomainPage getAllEntitiesByPage(Class<T> clazz, int pageIndex, int pageSize, String orderBy, SortBy sortBy) {
        pageIndex = pageIndex < 1 ? 1 : pageIndex;
        pageSize = pageSize < 1 ? 1 : pageSize;

        if (null == orderBy || "".equals(orderBy)) {
            orderBy = "id";
        }

        if (null == sortBy) {
            sortBy = SortBy.DESC;
        }

        StringBuilder buffer = new StringBuilder("select c from ");
        buffer.append(clazz.getName()).append(wherePattern);


        buffer.append(" order by c.").append(orderBy).append(spacePattern).append(sortBy.name());

        Query query = em.createQuery(buffer.toString());
        query.setFirstResult((pageIndex - 1) * pageSize);
        query.setMaxResults(pageSize);
        List<T> resultList = query.getResultList();

        long totalCount = this.getEntityTotalCount(clazz);
        DomainPage domainPage = new DomainPage(pageSize, pageIndex, totalCount);
        domainPage.getDomains().addAll(resultList);

        return domainPage;
    }

    @Override
    public DomainPage getEntitiesByPage(Class<T> clazz, String fieldName, Object fieldValue, int pageIndex, int pageSize) {
        pageIndex = pageIndex < 1 ? 1 : pageIndex;
        pageSize = pageSize < 1 ? 1 : pageSize;

        StringBuilder buffer = new StringBuilder("select c from ");
        buffer.append(clazz.getName()).append(wherePattern);
        buffer.append(andPattern).append("c.").append(fieldName).append(this.getFilterConditionByFieldValue(fieldValue, 1, false));
        buffer.append(" order by c.id desc");

        Query query = em.createQuery(buffer.toString());
        query.setParameter(1, fieldValue);
        query.setFirstResult((pageIndex - 1) * pageSize);
        query.setMaxResults(pageSize);
        List<T> resultList = query.getResultList();

        long totalCount = this.getEntityTotalCount(clazz, fieldName, fieldValue);
        DomainPage domainPage = new DomainPage(pageSize, pageIndex, totalCount);
        domainPage.getDomains().addAll(resultList);

        return domainPage;
    }

    @Override
    public DomainPage getEntitiesPagesByFieldList(Class<T> clazz, Map<String, Object> fieldNameValueMap, int pageIndex, int pageSize) {
        return this.getEntitiesPagesByFieldList(clazz, fieldNameValueMap, null, null, pageIndex, pageSize);
    }

    @Override
    public List<T> getEntitiesByFieldList(Class<T> clazz, Map<String, Object> fieldNameValueMap, String orderBy, SortBy sortBy) {
        StringBuilder buffer = new StringBuilder("select c from ").append(clazz.getName()).append(wherePattern);

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            buffer.append(andPattern);
            this.concatFilterConditionsByMap(fieldNameValueMap, buffer);
        }

        if (null == orderBy || "".equals(orderBy)) {
            orderBy = "id";
        }

        if (null == sortBy) {
            sortBy = SortBy.DESC;
        }

        buffer.append(" order by c.").append(orderBy).append(spacePattern).append(sortBy.name());

        Query query = em.createQuery(buffer.toString());

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            this.setFilterConditionsWithValues(fieldNameValueMap.values(), query);
        }

        return query.getResultList();
    }

    @Override
    public DomainPage getEntitiesPagesByFieldList(Class<T> clazz, Map<String, Object> fieldNameValueMap, String orderBy, SortBy sortBy, int pageIndex, int pageSize) {
        pageIndex = pageIndex < 1 ? 1 : pageIndex;
        pageSize = pageSize < 1 ? 1 : pageSize;

        StringBuilder buffer = new StringBuilder("select c from ").append(clazz.getName()).append(wherePattern);

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            buffer.append(andPattern);
            this.concatFilterConditionsByMap(fieldNameValueMap, buffer);
        }

        if (null == orderBy || "".equals(orderBy)) {
            orderBy = "id";
        }

        if (null == sortBy) {
            sortBy = SortBy.DESC;
        }

        buffer.append(" order by c.").append(orderBy).append(spacePattern).append(sortBy.name());
        Query query = em.createQuery(buffer.toString());

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            this.setFilterConditionsWithValues(fieldNameValueMap.values(), query);
        }

        query.setFirstResult((int) ((pageIndex - 1) * pageSize));
        query.setMaxResults((int) pageSize);
        List<T> resultList = query.getResultList();

        long totalCount = this.getEntityTotalCount(clazz, fieldNameValueMap);

        DomainPage domainPage = new DomainPage(pageSize, pageIndex, totalCount);
        domainPage.getDomains().addAll(resultList);

        return domainPage;
    }

    @Override
    public DomainPage queryEntitiesPagesByFieldList(Class<T> clazz, Map<String, Object> fieldNameValueMap, String orderBy, SortBy sortBy, int pageIndex, int pageSize) {
        if (null == fieldNameValueMap || fieldNameValueMap.isEmpty()) {
            return this.getAllEntitiesByPage(clazz, pageIndex, pageSize, orderBy, sortBy);
        }
        return this.getEntitiesPagesByFieldList(clazz, fieldNameValueMap, orderBy, sortBy, pageIndex, pageSize);
    }

    @Override
    public DomainPage getEntitiesPagesByCondition(Class<T> clazz, Condition condition, int pageIndex, int pageSize) {
        return this.getEntitiesPagesByCondition(clazz, condition, null, null, pageIndex, pageSize);
    }

    @Override
    public DomainPage getEntitiesPagesByCondition(Class<T> clazz, Condition condition, String orderBy, SortBy sortBy, int pageIndex, int pageSize) {
        pageIndex = pageIndex < 1 ? 1 : pageIndex;
        pageSize = pageSize < 1 ? 1 : pageSize;

        StringBuilder buffer = new StringBuilder("select c from ").append(clazz.getName()).append(wherePattern);

        //where
        if (condition != null && !condition.isEmpty())
            buffer.append(andPattern).append(condition.toString());

        //order by
        if (orderBy == null || orderBy.isEmpty())
            orderBy = "id";

        if (sortBy == null) {
            sortBy = SortBy.DESC;
        }
        buffer.append(" order by c.").append(orderBy).append(spacePattern).append(sortBy.name());

        Query query = em.createQuery(buffer.toString());

        if (condition != null)
            condition.setParameter(query, 1);
        query.setFirstResult((pageIndex - 1) * pageSize);
        query.setMaxResults(pageSize);

        List<T> resultList = query.getResultList();

        //count
        String sql = buffer.toString();
        String sqlCount = "select count(c) " + QueryUtil.removeSelect(QueryUtil.removeOrders(sql));
        query = em.createQuery(sqlCount);

        if (condition != null)
            condition.setParameter(query, 1);
        DomainPage domainPage = new DomainPage(pageSize, pageIndex, getTotalCount(query));
        domainPage.getDomains().addAll(resultList);
        return domainPage;
    }

    @Override
    public List<T> getEntitiesByCondition(Class<T> clazz, Condition condition) {
        return this.getEntitiesByCondition(clazz, condition, null, null);
    }

    @Override
    public List<T> getEntitiesByCondition(Class<T> clazz, Condition condition, String orderBy, SortBy sortBy) {
        StringBuilder buffer = new StringBuilder("select c from ").append(clazz.getName()).append(wherePattern);

        // where
        if (condition != null && !condition.isEmpty())
            buffer.append(andPattern).append(condition.toString());

        // order by
        if (orderBy == null || orderBy.isEmpty())
            orderBy = "id";

        if (sortBy == null) {
            sortBy = SortBy.DESC;
        }
        buffer.append(" order by c.").append(orderBy).append(spacePattern).append(sortBy.name());

        Query query = em.createQuery(buffer.toString());

        if (condition != null)
            condition.setParameter(query, 1);

        return query.getResultList();
    }

    @Override
    public DomainPage findEntitiesPagesByFieldList(Class<T> clazz, Map<String, Object> fieldNameValueMap, String orderBy, SortBy sortBy, int pageIndex, int pageSize) {
        pageIndex = pageIndex < 1 ? 1 : pageIndex;
        pageSize = pageSize < 1 ? 1 : pageSize;

        StringBuilder buffer = new StringBuilder("select c from ").append(clazz.getName()).append(wherePattern);

        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            buffer.append(andPattern);
            this.concatFilterConditionsByMap(fieldNameValueMap, buffer);
        }

        // order by
        if (orderBy == null || orderBy.isEmpty())
            orderBy = "id";

        if (sortBy == null) {
            sortBy = SortBy.DESC;
        }

        buffer.append(" order by c.").append(orderBy).append(spacePattern).append(sortBy.name());
        Query query = em.createQuery(buffer.toString());
        if (null != fieldNameValueMap && !fieldNameValueMap.isEmpty()) {
            this.setFilterConditionsWithValues(fieldNameValueMap.values(), query);
        }

        query.setFirstResult((int) ((pageIndex - 1) * pageSize));
        query.setMaxResults((int) pageSize);
        List<T> resultList = query.getResultList();

        long totalCount = this.getEntityTotalCount(clazz, fieldNameValueMap);

        DomainPage domainPage = new DomainPage(pageSize, pageIndex, totalCount);
        domainPage.getDomains().addAll(resultList);

        return domainPage;
    }


    /**
     * 根据查询条件参数来拼接过滤条件
     *
     * @param fieldNameValueMap
     * @param buffer
     * @return
     */
    protected void concatFilterConditionsByMap(Map<String, Object> fieldNameValueMap, StringBuilder buffer) {
        int pos = 1;
        Iterator<Map.Entry<String, Object>> iterator = fieldNameValueMap.entrySet().iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = iterator.next();
            String name = entry.getKey();
            if (null == name) {
                iterator.remove();
                continue;
            }

            if (1 != pos) {
                buffer.append(andPattern);
            }

            buffer.append("c.").append(name);
            if (null == entry.getValue()) {
                buffer.append(" is null");
                iterator.remove();
            } else {
                buffer.append(" = ?").append(pos);
            }
            pos++;
        }
    }

    /**
     * 根据查询条件参数值来设置过滤条件
     *
     * @param values
     * @param query
     * @return
     */
    protected void setFilterConditionsWithValues(final Collection<Object> values, Query query) {
        int pos = 1;
        for (Object value : values) {
            query.setParameter(pos++, value);
        }
    }

    /**
     * 获取查询结果集的个数
     *
     * @param query
     * @return
     */
    protected Long getTotalCount(Query query) {
        query.setMaxResults(1);

        Object count;
        try {
            count = query.getSingleResult();
        } catch (NoResultException e) {
            return 0L;
        }

        if (null != count) {
            if (count instanceof Long) {
                return (Long) count;
            } else if (count instanceof BigInteger) {
                return ((BigInteger) count).longValue();
            }
        }
        return 0L;
    }

    /**
     * 根据字段值来封装过滤条件
     *
     * @param fieldValue
     * @param pos
     * @return
     */
    protected String getFilterConditionByFieldValue(Object fieldValue, int pos, boolean byLike) {
        return null == fieldValue ? " is null" : (byLike ? " like ?" + pos : " = ?" + pos);
    }

    private String getFilterPattern(Class<T> clazz, String fieldName, Object fieldValue) {
        return this.supportLikePattern(clazz, fieldName, fieldValue) ? " like ?" : " = ?";
    }

    /**
     * 是否支持模糊查询
     * @param clazz
     * @param fieldName
     * @param fieldValue
     * @return
     */
    private boolean supportLikePattern(Class<T> clazz, String fieldName, Object fieldValue) {
        if (this.unSupportLikePatternByValue(fieldValue)) {
            return false;
        }
        /* 不支持值判断时才进行类属性检查 */
        return this.supportLikePatternByProperty(clazz, fieldName);
    }

    /**
     * 根据值来判断是否支持模糊查询
     *
     * @param fieldValue
     * @return
     */
    private boolean unSupportLikePatternByValue(Object fieldValue) {
        return (null == fieldValue) || (fieldValue instanceof Enum) || (fieldValue instanceof Boolean) || (fieldValue instanceof Integer) || (fieldValue instanceof Long) || (fieldValue instanceof BigDecimal) || (fieldValue instanceof Date);
    }

    /**
     * 根据字段来判断是否支持模糊查询
     * @param clazz
     * @param fieldName
     * @return
     */
    private boolean supportLikePatternByProperty(Class<T> clazz, String fieldName) {
        return !(this.isEnumType(clazz, fieldName) || this.isBoolType(clazz, fieldName) || this.isIntType(clazz, fieldName) || this.isLongType(clazz, fieldName) || this.isBigDecimalType(clazz, fieldName) || this.isDateType(clazz, fieldName));
    }

    /**
     * 是否是枚举类型
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    private <T extends BaseDomain> boolean isEnumType(Class<T> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            Object[] objs = field.getType().getEnumConstants();
            return null != objs;
        } catch (NoSuchFieldException e) {
            return false;
        }
    }

    /**
     * 是否是布尔类型
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    private <T extends BaseDomain> boolean isBoolType(Class<T> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            return field.getType().equals(Boolean.TYPE) || field.getType().equals(Boolean.class);
        } catch (NoSuchFieldException e) {
            return false;
        }
    }

    /**
     * 是否是int类型
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    private <T extends BaseDomain> boolean isIntType(Class<T> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            return field.getType().equals(Integer.TYPE) || field.getType().equals(Integer.class);
        } catch (NoSuchFieldException e) {
            return false;
        }
    }

    /**
     * 是否是Long类型
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    private <T extends BaseDomain> boolean isLongType(Class<T> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            return field.getType().equals(Long.TYPE) || field.getType().equals(Long.class);
        } catch (NoSuchFieldException e) {
            return false;
        }
    }

    /**
     * 是否是BigDecimal类型
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    private <T extends BaseDomain> boolean isBigDecimalType(Class<T> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            return field.getType().equals(BigDecimal.class);
        } catch (NoSuchFieldException e) {
            return false;
        }
    }

    private <T extends BaseDomain> boolean isDateType(Class<T> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName);
            return field.getType().equals(Date.class);
        } catch (NoSuchFieldException e) {
            return false;
        }
    }
}
